/**
 * Copyright (c) 2012-2016, www.tinygroup.org (luo_guo@icloud.com).
 *
 *  Licensed under the GPL, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.gnu.org/licenses/gpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.tinygroup.logger.impl;

import org.slf4j.MDC;
import org.slf4j.spi.MDCAdapter;
import org.tinygroup.commons.i18n.LocaleUtil;
import org.tinygroup.commons.tools.CollectionUtil;
import org.tinygroup.context.Context;
import org.tinygroup.i18n.I18nMessage;
import org.tinygroup.i18n.I18nMessageFactory;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.tinygroup.logger.LogLevel.ERROR;

/**
 * @author luoguo
 */
public class LoggerImpl implements Logger {
    /**
     * 日志缓存默认最大记录条数及最大字节数.
     */
    private static final int DEFAULT_MAX_BUFFER_RECORDS = 80000;
    private static Pattern pattern = Pattern.compile("[{](.)*?[}]");
    private static I18nMessage i18nMessage = I18nMessageFactory
            .getI18nMessages();
    // MDC适配器
    protected MDCAdapter mdc = MDC.getMDCAdapter();
    private org.slf4j.Logger logger;
    private boolean supportTransaction = false;
    private ThreadLocal<LogBuffer> threadLocal = new ThreadLocal<LogBuffer>();
    private Map<String, String> mdcMap = new HashMap<String, String>();
    /**
     * The max buffer records.
     */
    private int maxBufferRecords = DEFAULT_MAX_BUFFER_RECORDS;

    /**
     * The buffer records.
     */
    private int bufferRecords = 0;

    public LoggerImpl(org.slf4j.Logger logger) {
        this.logger = logger;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#isSupportTransaction()
     */
    public boolean isSupportTransaction() {
        return supportTransaction;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#setSupportTransaction(boolean)
     */
    public void setSupportTransaction(boolean supportBusiness) {
        this.supportTransaction = supportBusiness;
    }

    public void removeLogBuffer() {
        threadLocal.set(null);
    }

    public synchronized LogBuffer getLogBuffer() {
        if (supportTransaction) {
            LogBuffer logBuffer = threadLocal.get();
            if (logBuffer == null) {
                logBuffer = new LogBuffer();
                threadLocal.set(logBuffer);
            }
            return logBuffer;
        }
        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#startTransaction()
     */
    public void startTransaction() {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null) {
            logBuffer.increaseTransactionDepth();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#endTransaction()
     */
    public void endTransaction() {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null) {
            logBuffer.decreaseTransactionDepth();
            if (logBuffer.getTimes() == 0) {
                flushLog(logBuffer);
                removeLogBuffer();
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#flushTransaction()
     */
    public void flushTransaction() {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null) {
            flushLog(logBuffer);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#resetTransaction()
     */
    public void resetTransaction() {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null) {
            logBuffer.reset();
            maxBufferRecords = 0;
        }
    }

    private void flushLog(LogBuffer logBuffer) {
        for (Message message : logBuffer.getLogMessages()) {
            if (message.getThrowable() != null
                    && LogLevel.ERROR == message.getLevel()) {
                logError(message.getMessage(), message.getThrowable());
            } else if (message.getThrowable() != null
                    && LogLevel.ERROR != message.getLevel()) {
                pLogMessage(message.getLevel(), message.getMessage(),
                        message.getThrowable());
            } else {
                pLogMessage(message.getLevel(), message.getMessage());
            }
        }
        logBuffer.getLogMessages().clear();
        bufferRecords = 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#getLogger()
     */
    public org.slf4j.Logger getLogger() {
        return logger;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tinygroup.logger.ILogger#isEnabled(org.tinygroup.logger.LogLevel)
     */
    public boolean isEnabled(LogLevel logLevel) {
        switch (logLevel) {
            case DEBUG:
                return logger.isDebugEnabled();
            case INFO:
                return logger.isInfoEnabled();
            case WARN:
                return logger.isWarnEnabled();
            case TRACE:
                return logger.isTraceEnabled();
            case ERROR:
                return logger.isErrorEnabled();
        }
        return true;
    }

    /**
     * 直接记录日志
     *
     * @param logLevel
     * @param message
     */
    private void pLogMessage(LogLevel logLevel, String message) {
        putMdcVariable();// 在输出日志之前先放入局部线程变量中的mdc值
        switch (logLevel) {
            case DEBUG:
                logger.debug(message);
                break;
            case INFO:
                logger.info(message);
                break;
            case WARN:
                logger.warn(message);
                break;
            case TRACE:
                logger.trace(message);
                break;
            case ERROR:
                logger.error(message);
                break;
        }
        clearMdcVariable();
    }

    private void putMdcVariable() {
        MDC.clear();
        putMdcVariable(mdcMap);
        putMdcVariable(LoggerFactory.getThreadVariableMap());
    }

    private void clearMdcVariable() {
        MDC.clear();
    }

    private void putMdcVariable(Map<String, String> mdcMap) {
        if (!CollectionUtil.isEmpty(mdcMap)) {
            for (Entry<String, String> entry : mdcMap.entrySet()) {
                String value = entry.getValue();
                if (value != null) {
                    mdc.put(entry.getKey(), value);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tinygroup.logger.ILogger#logMessage(org.tinygroup.logger.LogLevel,
     * java.lang.String)
     */
    public void logMessage(LogLevel logLevel, String message) {
        if (!isEnabled(logLevel)) {
            return;
        }
        LogLevel threadLevel = LoggerFactory.getThreadLogLevel();
        if (threadLevel == null || threadLevel != null
                && logLevel.toString().equals(threadLevel.toString())) {
            exportLog(logLevel, message);
        }
    }

    private void exportLog(LogLevel logLevel, String message) {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null && logBuffer.getTimes() > 0) {
            logBuffer.getLogMessages().add(
                    new Message(logLevel, message, System.currentTimeMillis()));
            checkBufferSize(logBuffer);
        } else {
            pLogMessage(logLevel, message);
        }
    }

    public void logMessage(LogLevel logLevel, String message, Throwable t) {
        if (!isEnabled(logLevel)) {
            return;
        }
        LogLevel threadLevel = LoggerFactory.getThreadLogLevel();
        if (threadLevel == null || threadLevel != null
                && logLevel.toString().equals(threadLevel.toString())) {
            exportLog(logLevel, message, t);
        }
    }

    private void exportLog(LogLevel logLevel, String message, Throwable t) {
        LogBuffer logBuffer = getLogBuffer();
        if (logBuffer != null && logBuffer.getTimes() > 0) {
            logBuffer.getLogMessages().add(
                    new Message(logLevel, message, System.currentTimeMillis(),
                            t));
            checkBufferSize(logBuffer);
        } else {
            pLogMessage(logLevel, message, t);
        }
    }

    private void pLogMessage(LogLevel logLevel, String message, Throwable t) {
        putMdcVariable();// 在输出日志之前先放入局部线程变量中的mdc值
        switch (logLevel) {
            case DEBUG:
                logger.debug(message, t);
                break;
            case INFO:
                logger.info(message, t);
                break;
            case WARN:
                logger.warn(message, t);
                break;
            case TRACE:
                logger.trace(message, t);
                break;
            case ERROR:
                logger.error(message, t);
                break;
        }
        clearMdcVariable();
    }

    /**
     * 检测日志缓存列表，如果超过了最大限制条数或最大限制记录数，<br>
     * 则强制执行刷新操作。<br>
     * .
     *
     * @param logBuffer
     */
    private void checkBufferSize(LogBuffer logBuffer) {
        bufferRecords++;
        if (bufferRecords >= maxBufferRecords) {
            flushLog(logBuffer);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#log(org.tinygroup.logger.LogLevel,
     * java.util.Locale, java.lang.String, java.lang.Object)
     */
    public void log(LogLevel logLevel, Locale locale, String code,
                    Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, locale, null, args));
    }

    public void log(LogLevel logLevel, String code) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code));
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#log(org.tinygroup.logger.LogLevel,
     * java.lang.String, java.lang.Object)
     */
    public void log(LogLevel logLevel, String code, Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, LocaleUtil
                .getContext().getLocale(), null, args));
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#log(org.tinygroup.logger.LogLevel,
     * java.util.Locale, java.lang.String, org.tinygroup.context.Context)
     */
    public void log(LogLevel logLevel, Locale locale, String code,
                    Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, context, locale));
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tinygroup.logger.ILogger#log(org.tinygroup.logger.LogLevel,
     * java.lang.String, org.tinygroup.context.Context)
     */
    public void log(LogLevel logLevel, String code, Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, context));
    }

    public void logMessage(LogLevel logLevel, String message, Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, format(message, args));

    }

    public void logMessage(LogLevel logLevel, String message, Throwable t,
                           Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, format(message, args), t);

    }

    private String format(String message, Object... args) {
        Matcher matcher = pattern.matcher(message);
        StringBuilder stringBuffer = new StringBuilder();
        int start = 0;
        int count = 0;
        while (matcher.find(start)) {
            stringBuffer.append(message.substring(start, matcher.start()));
            stringBuffer.append(args[count++]);
            start = matcher.end();
            if (count == args.length) {
                break;
            }
        }
        stringBuffer.append(message.substring(start, message.length()));
        return stringBuffer.toString();
    }

    public void logMessage(LogLevel logLevel, String message, Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.format(message, context));
    }

    public void logMessage(LogLevel logLevel, String message, Throwable t,
                           Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.format(message, context), t);
    }

    public void error(String code, Object... args) {
        log(LogLevel.ERROR, code, args);
    }

    public void error(String code, Throwable throwable, Object... args) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logError(i18nMessage.getMessage(code, LocaleUtil.getContext()
                .getLocale(), null, args), throwable);
    }

    public void error(String code, Throwable throwable, Context context) {
        log(ERROR, code, throwable, context);
    }

    public void errorMessage(String message, Object... args) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logMessage(LogLevel.ERROR, message, args);
    }

    public void errorMessage(String message, Throwable throwable,
                             Object... args) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logError(format(message, args), throwable);
    }

    public void errorMessage(String message, Throwable throwable,
                             Context context) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logError(i18nMessage.format(message, context), throwable);
    }

    public void error(Throwable throwable) {
        logError(throwable.getMessage(), throwable);
    }

    private void logError(String message, Throwable throwable) {
        if (!isEnabled(ERROR)) {
            return;
        }
        LogLevel threadLevel = LoggerFactory.getThreadLogLevel();
        if (threadLevel == null || threadLevel != null
                && LogLevel.ERROR.toString().equals(threadLevel.toString())) {
            LogBuffer logBuffer = getLogBuffer();
            if (logBuffer != null && logBuffer.getTimes() > 0) {
                logBuffer.getLogMessages().add(
                        new Message(ERROR, message, System.currentTimeMillis(),
                                throwable));
                checkBufferSize(logBuffer);
            } else {
                putMdcVariable();
                logger.error(message, throwable);
                clearMdcVariable();
            }
        }

    }

    public void putToMDC(String key, Object value) {
        mdcMap.put(key, value.toString());
    }

    public void removeFromMDC(String key) {
        mdc.remove(key);
    }

    public int getMaxBufferRecords() {
        return maxBufferRecords;
    }

    public void setMaxBufferRecords(int maxBufferRecords) {
        this.maxBufferRecords = maxBufferRecords;
    }

    public void infoMessage(String message, Object... args) {
        if (!isEnabled(LogLevel.INFO)) {
            return;
        }
        logMessage(LogLevel.INFO, format(message, args));
    }

    public void infoMessage(String message, Throwable t, Object... args) {
        if (!isEnabled(LogLevel.INFO)) {
            return;
        }
        logMessage(LogLevel.INFO, format(message, args), t);
    }

    public void infoMessage(String message, Context context) {
        if (!isEnabled(LogLevel.INFO)) {
            return;
        }
        logMessage(LogLevel.INFO, i18nMessage.format(message, context));
    }

    public void infoMessage(String message, Throwable t, Context context) {
        if (!isEnabled(LogLevel.INFO)) {
            return;
        }
        logMessage(LogLevel.INFO, i18nMessage.format(message, context), t);
    }

    public void debugMessage(String message, Object... args) {
        if (!isEnabled(LogLevel.DEBUG)) {
            return;
        }
        logMessage(LogLevel.DEBUG, format(message, args));
    }

    public void debugMessage(String message, Throwable t, Object... args) {
        if (!isEnabled(LogLevel.DEBUG)) {
            return;
        }
        logMessage(LogLevel.DEBUG, format(message, args), t);
    }

    public void debugMessage(String message, Context context) {
        if (!isEnabled(LogLevel.DEBUG)) {
            return;
        }
        logMessage(LogLevel.DEBUG, i18nMessage.format(message, context));
    }

    public void debugMessage(String message, Throwable t, Context context) {
        if (!isEnabled(LogLevel.DEBUG)) {
            return;
        }
        logMessage(LogLevel.DEBUG, i18nMessage.format(message, context), t);
    }

    public void warnMessage(String message, Object... args) {
        if (!isEnabled(LogLevel.WARN)) {
            return;
        }
        logMessage(LogLevel.WARN, format(message, args));
    }

    public void warnMessage(String message, Throwable t, Object... args) {
        if (!isEnabled(LogLevel.WARN)) {
            return;
        }
        logMessage(LogLevel.WARN, format(message, args), t);
    }

    public void warnMessage(String message, Context context) {
        if (!isEnabled(LogLevel.WARN)) {
            return;
        }
        logMessage(LogLevel.WARN, i18nMessage.format(message, context));
    }

    public void warnMessage(String message, Throwable t, Context context) {
        if (!isEnabled(LogLevel.WARN)) {
            return;
        }
        logMessage(LogLevel.WARN, i18nMessage.format(message, context), t);
    }

    public void traceMessage(String message, Object... args) {
        if (!isEnabled(LogLevel.TRACE)) {
            return;
        }
        logMessage(LogLevel.TRACE, format(message, args));
    }

    public void traceMessage(String message, Throwable t, Object... args) {
        if (!isEnabled(LogLevel.TRACE)) {
            return;
        }
        logMessage(LogLevel.TRACE, format(message, args), t);
    }

    public void traceMessage(String message, Context context) {
        if (!isEnabled(LogLevel.TRACE)) {
            return;
        }
        logMessage(LogLevel.TRACE, i18nMessage.format(message, context));
    }

    public void traceMessage(String message, Throwable t, Context context) {
        if (!isEnabled(LogLevel.TRACE)) {
            return;
        }
        logMessage(LogLevel.TRACE, i18nMessage.format(message, context), t);
    }

    public void log(LogLevel logLevel, Locale locale, String code, Throwable t,
                    Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, locale, null, args),
                t);
    }

    public void log(LogLevel logLevel, String code, Throwable t, Object... args) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, null, args), t);
    }

    public void log(LogLevel logLevel, Locale locale, String code, Throwable t,
                    Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, context));
    }

    public void log(LogLevel logLevel, String code, Throwable t, Context context) {
        if (!isEnabled(logLevel)) {
            return;
        }
        logMessage(logLevel, i18nMessage.getMessage(code, context), t);
    }

    public void info(Locale locale, String code, Object... args) {
        log(LogLevel.INFO, locale, code, args);
    }

    public void info(Locale locale, String code, Throwable t, Object... args) {
        log(LogLevel.INFO, locale, code, t, args);
    }

    public void info(String code, Object... args) {
        log(LogLevel.INFO, code, args);
    }

    public void info(String code, Throwable t, Object... args) {
        log(LogLevel.INFO, code, t, args);
    }

    public void info(Locale locale, String code, Context context) {
        log(LogLevel.INFO, locale, code, context);
    }

    public void info(Locale locale, String code, Throwable t, Context context) {
        log(LogLevel.INFO, locale, code, t, context);
    }

    public void info(String code, Context context) {
        log(LogLevel.INFO, code, context);
    }

    public void info(String code, Throwable t, Context context) {
        log(LogLevel.INFO, code, t, context);
    }

    public void debug(Locale locale, String code, Object... args) {
        log(LogLevel.DEBUG, locale, code, args);
    }

    public void debug(Locale locale, String code, Throwable t, Object... args) {
        log(LogLevel.DEBUG, locale, code, t, args);
    }

    public void debug(String code, Object... args) {
        log(LogLevel.DEBUG, code, args);
    }

    public void debug(String code, Throwable t, Object... args) {
        log(LogLevel.DEBUG, code, t, args);
    }

    public void debug(Locale locale, String code, Context context) {
        log(LogLevel.DEBUG, locale, code, context);
    }

    public void debug(Locale locale, String code, Throwable t, Context context) {
        log(LogLevel.DEBUG, locale, code, t, context);
    }

    public void debug(String code, Context context) {
        log(LogLevel.DEBUG, code, context);
    }

    public void debug(String code, Throwable t, Context context) {
        log(LogLevel.DEBUG, code, t, context);
    }

    public void warn(Locale locale, String code, Object... args) {
        log(LogLevel.WARN, locale, code, args);
    }

    public void warn(Locale locale, String code, Throwable t, Object... args) {
        log(LogLevel.WARN, locale, code, t, args);
    }

    public void warn(String code, Object... args) {
        log(LogLevel.WARN, code, args);
    }

    public void warn(String code, Throwable t, Object... args) {
        log(LogLevel.WARN, code, t, args);
    }

    public void warn(Locale locale, String code, Context context) {
        log(LogLevel.WARN, locale, code, context);
    }

    public void warn(Locale locale, String code, Throwable t, Context context) {
        log(LogLevel.WARN, locale, code, t, context);
    }

    public void warn(String code, Context context) {
        log(LogLevel.WARN, code, context);
    }

    public void warn(String code, Throwable t, Context context) {
        log(LogLevel.WARN, code, t, context);
    }

    public void trace(Locale locale, String code, Object... args) {
        log(LogLevel.TRACE, locale, code, args);
    }

    public void trace(Locale locale, String code, Throwable t, Object... args) {
        log(LogLevel.TRACE, locale, code, t, args);
    }

    public void trace(String code, Object... args) {
        log(LogLevel.TRACE, code, args);
    }

    public void trace(String code, Throwable t, Object... args) {
        log(LogLevel.TRACE, code, t, args);
    }

    public void trace(Locale locale, String code, Context context) {
        log(LogLevel.TRACE, locale, code, context);
    }

    public void trace(Locale locale, String code, Throwable t, Context context) {
        log(LogLevel.TRACE, locale, code, t, context);
    }

    public void trace(String code, Context context) {
        log(LogLevel.TRACE, code, context);
    }

    public void trace(String code, Throwable t, Context context) {
        log(LogLevel.TRACE, code, t, context);
    }

    public void error(Locale locale, String code, Object... args) {
        log(LogLevel.ERROR, locale, code, args);
    }

    public void error(Locale locale, String code, Throwable throwable,
                      Object... args) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logError(i18nMessage.getMessage(code, locale, null, args), throwable);
    }

    public void error(Locale locale, String code, Throwable throwable,
                      Context context) {
        log(LogLevel.ERROR, locale, code, throwable, context);
    }

    public void errorMessage(String message, Context context) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logMessage(LogLevel.ERROR, message, context);
    }

    public void error(String code, Context context) {
        log(LogLevel.ERROR, code, context);
    }

    public void error(Locale locale, String code, Context context) {
        log(LogLevel.ERROR, locale, code, context);
    }

    public void error(String code) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logMessage(LogLevel.ERROR, i18nMessage.getMessage(code));
    }

    public void error(String code, Throwable throwable) {
        if (!isEnabled(LogLevel.ERROR)) {
            return;
        }
        logError(i18nMessage.getMessage(code), throwable);
    }

}
